#ifndef _CURVE_H_
#define _CURVE_H_

/**
* @file curve.h
* August 1, 2008
* 
* @brief Interface for the curve objects.
*
* This is a base class that allows the access to the behaviour of a
* three-dimensional curve object, which can be a LINE, POLYLINE, ARC,
* SPLINE etc.
* 
* @author Antonio Miranda (amiranda@tecgraf.puc-rio.br )
* @author Andre Pereira (brabo@tecgraf.puc-rio.br)
* 
* @see CurveFactory
*/

// includes
//----------
#include <vector>
#include <set>
#include "../box3d.h"
#include "../point3d.h"
#include "draw.h"


using namespace std;


// interface definition
//----------------------
class Curve
{
public:
  Curve();
  virtual ~Curve();

  // types of curves
  enum ECurveType
  {
     UNDEF,
     LINE,
     POLYLINE,
     ARC,
     SPLINE,
     JOINED,
     NCTYPE
  };

  //////////////////////////////////////////////////////////////////////////
  /// Methods Implemented INLINE 
  //////////////////////////////////////////////////////////////////////////

  // get/set methods
  void setId( int _id );
  void setSubdvision( int _n );
  void setRatio( double _ratio );
  virtual void updateData( int dg, int nP, int nU, double* P, double* U ) = 0;
  int getId();
  int getSubdvision();
  double getRatio();
  ECurveType getType();
 
  // set/get any additional generic information
  void setAttribute( void *_info );
  void getAttribute( void **_info );

  // set the methods to draw the curve
  void setDraw( drawGRA *_obj );
  drawGRA * getDraw( ) {return m_pDrawObj; }


  //////////////////////////////////////////////////////////////////////////
  /// Methods Implemented in CPP 
  //////////////////////////////////////////////////////////////////////////

  // get points on curve based on its subdivision and ratio
  void getSubdvision( vector<Point3D>& _subd );

  // get adaptive curve subdivision
  void getAdaptSubdvision (const double _maxAngle, vector<double>& _pa,
                           bool adapt_only = false);

  // get parametric subdivision
  void getParamSubdivision (vector<double>& _pa);

  // get tangent vectors on Curve based its subdivision and ratio
  void getTangent( vector<Point3D>& _tang );

  // get bounding box
  void getBox( double &xmin, double &ymin, double &zmin, 
               double &xmax, double &ymax, double &zmax )
  {
    m_box.get(xmin, ymin, zmin, xmax, ymax, zmax);
  }

  // check for pick 
  bool pick (double x, double y, double z, double tol);

  //////////////////////////////////////////////////////////////////////////
  /// Virtual Methods
  //////////////////////////////////////////////////////////////////////////

  // get curve name
  virtual void getName (char *name) = 0;

  // get the key point of the curve
  virtual vector<Point3D> getCurvePoints() = 0;
  // this is not a general implementation, needs specific implementation to arc and spline
  virtual vector<Point3D> getPolygonalPoints()=0;

  // get points on Curve based on given number of subdivision and ratio
  virtual void getSubdvision( int _n,
                              double _ratio,
                              vector<Point3D>& _subv ) = 0;

  // get tangent vectors on Curve based on given number of subdivision and ratio
  virtual void getTangent( int _nd,
                           double _ratio,
                           vector<Point3D>& _tang ) = 0;

  // get the total length from the curve
  virtual double getLenght() = 0;
  
  // projects a given point in the curve. 
  virtual int clstPoint( Point3D _pt,
                         double& _t,
                         Point3D& _pt_on,
                         double _tol ) = 0;

  // given a parametric value t (0-1), it returns the coordinates and tangent of point
  virtual void get3dPoint( double _t,
                           Point3D& _pt,
                           Point3D& _tg ) = 0;

  // TODO: given a parametric value t (0-1), it returns the coordinates
  virtual Point3D get3dPoint( double _t ) = 0;

  // draw the curve using the object set in setDraw method
  virtual void draw() = 0;

  // set the bound box of current curve
  virtual void setBox() = 0;
 
 
  //////////////////////////////////////////////////////////////////////////
  /// Methods to add point to the curve
  //////////////////////////////////////////////////////////////////////////

  // begin add point - the application can insert an additional information
  //                   the each curve know how to deal
  // For example, in arc curve, this vector is the normal of plane
  virtual int beginAddPoint( const vector<Point3D>& _addp ) = 0;
          int beginAddPoint( double _x,
                             double _y,
                             double _z );

  // add a point to the curve
  virtual int addPoint( Point3D& _pt ) = 0;
          int addPoint( double _x,
                        double _y,
                        double _z );

  virtual int addPoint( Point3D& _pt, double _tol ) = 0;
          int addPoint( double _x,
                        double _y,
                        double _z,
                        double _tol );

  // add a temporary point - using this method, the curve will be drawn
  virtual void addTempPoint( Point3D& _pt ) = 0;
          void addTempPoint( double _x,
                             double _y,
                             double _z );

  // Analogous to above, but for orthogonal drawing
  virtual void addTempOrthoPoint( Point3D& _pt ) = 0;
          void addTempOrthoPoint( double _x,
                                  double _y,
                                  double _z );

  // Analogous to above, but for orthogonal drawing
  virtual Point3D getLastTempPoint( ) = 0;

  // end add point and the curve is finally created
  virtual int endAddPoint() = 0;

  // get end points from curve
  virtual void getEndPoints( Point3D _pt[2] ) = 0;

  //////////////////////////////////////////////////////////////////////////

  // delete the last point from the Curve
  virtual void delPoint( Point3D& _pt ) = 0;

  // try to delete a point in the curve
  virtual void delPoint( Point3D& _pt,
                         double _tol ) = 0;

  // copy the current curve to another curve
  virtual void copy( Curve **_cp ) = 0;

  // transform the curve based on matrix transformation
  virtual void transform( double _mt[4][4] ) = 0;

  // invert points in the curve
  virtual void invert() = 0;

  // check if the curve is possible
  virtual int possible() = 0;

  // prints the coordinates of the points of the curve
  virtual void print() = 0;

  //////////////////////////////////////////////////////////////////////////
  /// Methods to edit the curve
  //////////////////////////////////////////////////////////////////////////

  // these methods create curves the intersect to a plane
  // 1 - The plane is defined by a normal and d constant.
  virtual int planeIntersect( Point3D& _n,
                             double _d, 
                             vector<Curve*>& _curves,
                             vector<Point3D>& _pts) = 0;

  // 2 - The plane is defined by an point on the plane and its normal.
  virtual int planeIntersect( Point3D& _orig,
                             Point3D& _n, 
                             vector<Curve*>& _curves,
                             vector<Point3D>& _pts) = 0;

  // join the current curve with a given curve. The result is a third curve 
  virtual int join( Curve* _givenCurve, Curve** _thirdCurve ) = 0;
  Curve* join( Curve* _otherCurve, double _tol );
  
  // split the current curve based on its subdivision and ratio
  virtual int split( vector<Curve*>& _curves ) = 0;

  // split the current curve based on given subdivision and ratio
  virtual int split( int _subdvision,
                     double _ratio,
                     vector<Curve*>& _curves ) = 0;

  // extracts from the current curve another curve based on the given interval
  virtual Curve* split( double _t0, double _t1, double _tol ) = 0;


  bool getFirstDraw()
  {
    return m_firstDraw;
  }

  // virtual read/write
  //bool read  (FILE *f) = 0;
  //bool write (FILE *f) = 0;


protected:
  int        m_id;        // Curve id
  ECurveType m_type;      // type (sub-class)
  int        m_subdv;     // subdivisions
  double     m_ratio;     // subdivision ratio
  void       *m_attr;     // attributes
  drawGRA*   m_pDrawObj;  // object used to draw curves
  Box3D      m_box;       // bounding box

  // variable used to temporary draw the curve
  bool m_firstDraw;
  Point3D m_lastPt;

  // auxiliary functions to derivate classes:

  // given the number of subdivisions and ratio, return 'pa' vector that contains the 
  // subdivision in parametric space (0-1). pa.lenght = n+1
  void getParamSubdivion( int _n,
                          double _r,
                          vector<double>& _pa );

  // given a set of point p, that can represent a polyline, return the position of
  // these points in parametric space 0-1.
  void getParamPositions( const vector<Point3D>& _p,
                          vector<double>& _s );

  // given a line represented by LineStart-LineEnd points, return the distance d from this line to
  // point Point. Obs: its returns 0 if the point p does not lie on the line
  int distancePoint2Line( Point3D _LineStart,
                          Point3D _LineEnd,
                          Point3D _Point, 
                          Point3D& _Intersection,
                          double& _d );

  // given a line segment represented by LineStart-LineEnd points, return the distance d from this
  // line segment to point Point. Obs: it returns always 1.
  int distancePoint2Segment( Point3D _LineStart,
                             Point3D _LineEnd,
                             Point3D _Point, 
                             Point3D& _Intersection,
                             double& _d );

  // check if the current size of a segment is according with an adaptive
  // equation (paper of adaptive - AMiranda)
  bool checkAdaptiveEquation (const double _maxAngle, const double _li, const double _di);
  void adaptiveRefinement     (const double _maxAngle, 
                              const double _par_i, 
                              const double _par_j, 
                              const double _len,
                              set <double> &_param);

};

//////////////////////////////
///     Methods INLINE     ///
//////////////////////////////

//
/////////////////////////////////////////////////////////////////////
inline
Curve::ECurveType
Curve::getType()
{
  return m_type;
}

//
/////////////////////////////////////////////////////////////////////
inline
int
Curve::getId()
{
  return m_id;
}

//
/////////////////////////////////////////////////////////////////////
inline
void
Curve::setId( int _id )
{ 
  m_id = _id;
}

//
/////////////////////////////////////////////////////////////////////
inline
void
Curve::setSubdvision( int _n )
{
  m_subdv = _n;
}

//
/////////////////////////////////////////////////////////////////////
inline
int
Curve::getSubdvision()
{
  return m_subdv;
};

//
/////////////////////////////////////////////////////////////////////
inline
void
Curve::setRatio( double _ratio )
{ 
  m_ratio = _ratio;
}

//
/////////////////////////////////////////////////////////////////////
inline
double
Curve::getRatio()
{ 
  return m_ratio;
}

//
/////////////////////////////////////////////////////////////////////
inline
void
Curve::setAttribute( void *_info )
{ 
  m_attr = _info;
}

//
/////////////////////////////////////////////////////////////////////
inline
void
Curve::getAttribute( void **_info )
{ 
  *_info = m_attr;
}

//
/////////////////////////////////////////////////////////////////////
inline
void
Curve::setDraw( drawGRA* _obj )
{ 
  m_pDrawObj = _obj;
}

//
/////////////////////////////////////////////////////////////////////
inline
void
Curve::getParamSubdivision( vector<double>& _pa )
{
  getParamSubdivion (m_subdv, m_ratio, _pa);
}


/////////////////////////////////////////////////////////////////////
/// Methods to add point to the curve
/////////////////////////////////////////////////////////////////////

//
/////////////////////////////////////////////////////////////////////
inline
int
Curve::beginAddPoint( double _x,
                      double _y,
                      double _z )
{
  Point3D pts( _x, _y, _z );
  vector<Point3D> addp;
  addp.push_back( pts );
  return beginAddPoint( addp );
}

//
/////////////////////////////////////////////////////////////////////
inline 
int
Curve::addPoint( double _x,
                 double _y,
                 double _z )
{
  Point3D pts( _x, _y, _z );
  return addPoint( pts );
}
//
/////////////////////////////////////////////////////////////////////
inline 
int 
Curve::addPoint( double _x,
                 double _y,
                 double _z,
                 double _tol )
{
  Point3D pts( _x, _y, _z );
  return addPoint( pts, _tol );
}

//
/////////////////////////////////////////////////////////////////////
inline 
void 
Curve::addTempPoint( double _x,
                     double _y,
                     double _z )
{
  Point3D pts( _x, _y, _z );
  return addTempPoint( pts );
}

//
/////////////////////////////////////////////////////////////////////
inline 
void 
Curve::addTempOrthoPoint( double _x,
                          double _y,
                          double _z )
{
  Point3D pts( _x, _y, _z );
  return addTempOrthoPoint( pts );
}

//
/////////////////////////////////////////////////////////////////////
inline 
Point3D
Curve::getLastTempPoint()
{
  return getLastTempPoint();
}

#endif // _CURVE_H_
